package com.zhou.common.mybatis.template.shiro.swagger;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Function;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import org.springframework.core.annotation.Order;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.ExpandedParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.google.common.collect.Lists.transform;
import static springfox.documentation.schema.Collections.collectionElementType;
import static springfox.documentation.schema.Collections.isContainerType;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER;

@Order(SWAGGER_PLUGIN_ORDER + 1001)
public class EnumDescriptionExpandedParameterBuilder implements ExpandedParameterBuilderPlugin {
    private final DescriptionResolver descriptions;
    private final EnumTypeDeterminer enumTypeDeterminer;
    private final TypeResolver resolver;

    public EnumDescriptionExpandedParameterBuilder(
            DescriptionResolver descriptions,
            TypeResolver typeResolver,
            EnumTypeDeterminer enumTypeDeterminer) {
        this.resolver = typeResolver;
        this.descriptions = descriptions;
        this.enumTypeDeterminer = enumTypeDeterminer;
    }

    @Override
    public void apply(ParameterExpansionContext context) {
        Optional<ApiModelProperty> apiModelPropertyOptional = context.findAnnotation(ApiModelProperty.class);
        if (apiModelPropertyOptional.isPresent()) {
            fromApiModelProperty(context, apiModelPropertyOptional.get());
        }
        Optional<ApiParam> apiParamOptional = context.findAnnotation(ApiParam.class);
        if (apiParamOptional.isPresent()) {
            fromApiParam(context, apiParamOptional.get());
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }

    private void fromApiParam(ParameterExpansionContext context, ApiParam apiParam) {
        context.getParameterBuilder()
                .description(description(context, apiParam.value()));
    }

    private void fromApiModelProperty(ParameterExpansionContext context, ApiModelProperty apiModelProperty) {
        context.getParameterBuilder()
                .description(description(context, apiModelProperty.value()));
    }

    private String description(ParameterExpansionContext context, String value) {
        value = descriptions.resolve(value);

        //判断是否是枚举
        ResolvedType resolved = this.resolver.resolve(context.getFieldType());
        if (isContainerType(resolved)) {
            resolved = fieldType(context).orElse(resolved);
            ResolvedType elementType = collectionElementType(resolved);
            if (enumTypeDeterminer.isEnum(elementType.getErasedType())) {
                return value + ":" + enumValues(elementType.getErasedType());
            }
        } else if (enumTypeDeterminer.isEnum(resolved.getErasedType())) {
            return value + ":" + enumValues(resolved.getErasedType());
        }
        return value;
    }

    private Optional<ResolvedType> fieldType(ParameterExpansionContext context) {
        return Optional.of(context.getFieldType());
    }

    private List<String> enumValues(final Class<?> subject) {

        if (EnumDescription.class.isAssignableFrom(subject)) {
            EnumDescription[] enumConstants = (EnumDescription[]) subject.getEnumConstants();
            return Arrays.stream(enumConstants).map(f -> f.getCode() + f.getInfo()).collect(Collectors.toList());
        }
        return transform(Arrays.asList(subject.getEnumConstants()), (Function<Object, String>) Object::toString);

    }
}
